package fr.lip6.meta.ple.generation.aspectj;

import java.io.ByteArrayInputStream;
import java.io.File;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.PrintStream;
import java.util.ArrayList;
import java.util.Collection;

import org.xml.sax.Attributes;
import org.xml.sax.InputSource;
import org.xml.sax.SAXException;
import org.xml.sax.SAXParseException;
import org.xml.sax.XMLReader;
import org.xml.sax.helpers.DefaultHandler;
import org.xml.sax.helpers.XMLReaderFactory;

import artefact.generic.ConstructionPrimitive;
import artefact.umlClassDiagram.CreateAssociation;
import artefact.umlClassDiagram.CreateAttribute;
import artefact.umlClassDiagram.CreateClass;
import artefact.umlClassDiagram.CreateGeneralization;
import artefact.umlClassDiagram.CreateOperation;
import artefact.umlClassDiagram.CreateOperationRefinement;
import artefact.umlClassDiagram.CreatePackage;
import artefact.umlClassDiagram.impl.CreateAttributeImpl;

import fr.lip6.meta.ple.constraintsextraction.constraint.Constraint;
import fr.lip6.meta.ple.featureIdentification.Feature;
import fr.lip6.meta.ple.featureIdentification.Product;
import fr.lip6.meta.ple.generation.strategy.ProductLineConstructionStrategy;
import fr.lip6.meta.ple.generation.strategy.ReadXmiStrategy;
import fr.lip6.meta.ple.generation.tools.PLConstructionUtil;
import fr.lip6.meta.tools.StatementPrimitive;
import fr.lip6.meta.tools.Trigger;

/**
 * Features generation to AspectJ
 *
 */
public class ProductLineConstructionAJ implements ProductLineConstructionStrategy {
	private static String rootDir = "./SPL-Construction-AJ/";
	private static boolean indentXMLModel = true;
	private PLConstructionFactoryAJ factory = new PLConstructionFactoryAJ(rootDir+"src");
	private Collection<Product> products = null;
	//private HashMap<String, ArrayList<Trigger>> refineClasses;
	//private HashMap<String, ArrayList<Trigger>> newClassesFeatures;

	public ProductLineConstructionAJ() {
		File outputDir = new File (rootDir);
		File outputFeaturesRootDir = new File(factory.getSrcDir());

		PLConstructionUtil.deleteRecursive(outputDir);
		outputDir.mkdirs();
		outputFeaturesRootDir.mkdirs();
	}

	/* (non-Javadoc)
	 * @see fr.lip6.meta.ple.generation.strategy.ProductLineConstructionStrategy#generateFeatureModel(java.util.Collection, java.util.ArrayList)
	 */
	public void generateFeatureModel(Collection<Feature> features, 
				ArrayList<Constraint> rules, String model_dir) {
		PrintStream out;
		File dotFile = new File(rootDir+"model.xml");
		FileOutputStream fout;
		IndentHandler indentHandler;
		String xml = "";

		try {
			fout = new FileOutputStream(dotFile);
			out = new PrintStream(fout);
			indentHandler = new IndentHandler(out);

			xml += "<?xml version=\"1.0\" encoding=\"UTF-8\" standalone=\"no\"?>"+"\n";
			xml += "<featureModel>"+"\n";
			xml += "<struct>"+"\n";
			xml += "<and abstract=\"true\" mandatory=\"true\" name=\"SPLConstructionAJ\">"+"\n";

			for (Feature parentFeat : features) {
				String parentFeatName[] = parentFeat.getId().split("_");
				boolean isParentFeatMandatory = PLConstructionUtil.isMandatory(parentFeat, products);

				//System.out.println("generateFeatureModel(): feature "+parentFeat.getId()+": "+parentFeat);

				if (parentFeatName.length == 1) {
					ArrayList<Feature> subFeatLvl1 = PLConstructionUtil.getSubfeaturesLevel1(parentFeat.getId(), features);

					if (subFeatLvl1.size() > 0) {
						if (isParentFeatMandatory) {
							xml += "<and abstract=\"true\" mandatory=\"true\" name=\""+parentFeat.getId()+"\""+" >"+"\n";
						} else {
							xml += "<and abstract=\"true\" name=\""+parentFeat.getId()+"\""+" >"+"\n";
						}

						for(Feature f1 : subFeatLvl1) {
							//String packageName = completePackageName(getFeaturePackageName(parentFeat));
							ArrayList<Feature> subFeatLvl2 = PLConstructionUtil.getSubfeaturesLevel2(f1.getId(), features);
							boolean isF1Mandatory = PLConstructionUtil.isMandatoryChild(f1, parentFeat, products);

							if (subFeatLvl2.size() > 0) {
								xml	+=	"<alt abstract=\"true\" "
										+	(isF1Mandatory ? "mandatory=\"true\" " : "")
										+	"name=\""+f1.getName()+"\" >"+"\n";

								for(Feature f2 : subFeatLvl2) {
									xml += getFeatureLvl2XML(	f2,
											true,
											PLConstructionUtil.isMandatoryChild(f2, f1, products)	);
								}

								xml	+=	"</alt>"+"\n";
							} else {
								xml	+=	"<feature "
										+	(isF1Mandatory ? "mandatory=\"true\" " : "")
										+	"name=\""+f1.getName()+"\" />"+"\n";
							}
						}
						xml += "</and>"+"\n";
					} else {
						xml += getFeatureLvl0XML(	parentFeat,
								false,
								isParentFeatMandatory);
					}
				}
			}

			xml += "</and>"+"\n";
			xml += "</struct>"+"\n";
			xml += "<constraints>"+"\n";

			//System.out.println("####### FEATURE MODEL CONSTRAINTS #######\n"+rules.size()+" rules");

			for(Constraint rule : rules) {
				xml += "<rule>"+"\n";
				xml += "<eq>"+"\n";

				xml += rule.getLeft().toXML()+"\n";

				xml += rule.getRight().toXML()+"\n";
				xml += "</eq>"+"\n";
				xml += "</rule>"+"\n";
			}

			xml += "</constraints>"+"\n";
			xml += "<comments/>"+"\n";

			xml += "</featureModel>"+"\n";

			// indentation of the feature model xml code
			if (indentXMLModel)
				indentHandler.indentXml(xml, out);
			else
				out.print(xml);
		} catch (FileNotFoundException e) {
			System.err.println("generateFeatureModel(): "+e.getMessage());
		}
	}

	
	/* (non-Javadoc)
	 * @see fr.lip6.meta.ple.generation.strategy.ProductLineConstructionStrategy#construct(java.util.Collection, java.util.Collection)
	 */
	@Override
	public void construct(Collection<Feature> features, Collection<Product> products) {
		try {
			for(Feature feat : features) {
				//System.out.println("\nProductLineConstructionAJ.construct(): FEATURE "+feat.getId()+":\n"+feat+"\n");
				generate(feat);
			}
		} catch (FileNotFoundException e) {
			System.err.println(e.getMessage());
		}
	}

	public void generate(Feature feat) throws FileNotFoundException {
		ArrayList<String> featClasses = new ArrayList<String>();
		//ArrayList<String> attributsName = new ArrayList<String>();
		//ArrayList<String> operationsName = new ArrayList<String>();
		//File featDir;
		String featName;

		if (feat.getId().split("_").length > 1) {
			featName = feat.getName();
		} else {
			featName = feat.getId();
		}

		//System.out.println("ProductLineConstructionAJ.generate(): "+featName);

		//featDir = new File(factory.getSrcDir()+"/"+featName);
		//featDir.mkdir();

		for(Trigger trigger : feat) {
			StatementPrimitive stPrimitive = (StatementPrimitive) trigger;
			ConstructionPrimitive consPrimitive = stPrimitive.getPrimitive();
			@SuppressWarnings("unused")
			PrintStream out;

			//System.out.println("Package base classes: "+PLConstructionFactoryAJ.packageBaseClasses.keySet());
			//System.out.println("Feature classes: "+featClasses);
			//System.out.println("Feature: "+featName+" ; construction primitive: "+consPrimitive);

			/*
			 * stPrimitive.getPrimitive() (ConstructionPrimtive) instances (7):
			 */

			// artefact.umlClassDiagram.CreatePackage
			if (consPrimitive instanceof CreatePackage) {
				//System.out.println("[[ProductLineConstructionAJ.generate(): CREATE PACKAGE");
				out = factory.createPackage(stPrimitive, feat, featClasses);
				//System.out.println("ProductLineConstructionAJ.generate(): CREATE PACKAGE]]]");
			}

			// artefact.umlClassDiagram.CreateClass
			if (consPrimitive instanceof CreateClass) {
				CreateClass crClass = (CreateClass) consPrimitive;

				if (!(featClasses.contains(crClass.getName()))) {
					//System.out.println("[[ProductLineConstructionAJ.generate(): CREATE CLASS");
					out = factory.createClass(crClass, feat, null);
					//System.out.println("ProductLineConstructionAJ.generate(): CREATE CLASS]]]");
				}
			}

			// artefact.umlClassDiagram.CreateGeneralization (inheritance)
			if (consPrimitive instanceof CreateGeneralization) {
				CreateGeneralization crGen = (CreateGeneralization) consPrimitive;

				if (!(featClasses.contains(crGen.getSub()))) {
					//System.out.println("[[ProductLineConstructionAJ.generate(): CREATE GENERALIZATION");
					out = factory.createGeneralization(crGen, feat, null);
					//System.out.println("ProductLineConstructionAJ.generate(): CREATE GENERALIZATION]]]");
				}
			}

			/*
			 * aspects possibles -->
			 */

			// artefact.umlClassDiagram.CreateAttribute
			if (consPrimitive instanceof CreateAttribute) {
				CreateAttributeImpl crAttr = (CreateAttributeImpl) consPrimitive;

				if (!(featClasses.contains(crAttr.getOwner()))) {
					//System.out.println("[[ProductLineConstructionAJ.generate(): CREATE ATTRIBUTE");
					out = factory.createAttribute(crAttr, feat, featClasses);
					//System.out.println("ProductLineConstructionAJ.generate(): CREATE ATTRIBUTE]]]");
				}
			}

			// artefact.umlClassDiagram.CreateOperation
			if (consPrimitive instanceof CreateOperation) {
				CreateOperation crOp = (CreateOperation) consPrimitive;

				if (!(featClasses.contains(crOp.getOwener()))) {
					//System.out.println("[[ProductLineConstructionAJ.generate(): CREATE OPERATION");
					out = factory.createOperation(crOp, feat, featClasses);
					//System.out.println("ProductLineConstructionAJ.generate(): CREATE OPERATION]]]");
				}
			}

			/*
			 * <-- aspects possibles
			 */

			// artefact.umlClassDiagram.CreateOperationRefinement
			if (consPrimitive instanceof CreateOperationRefinement) {
				CreateOperationRefinement crOpRef = (CreateOperationRefinement) consPrimitive;

				if (!(featClasses.contains(crOpRef.getOwner()))) {
					//System.out.println("[[ProductLineConstructionAJ.generate(): CREATE OPERATION REFINEMENT");
					out = factory.createOperationRefinement(crOpRef, feat, featClasses);
					//System.out.println("ProductLineConstructionAJ.generate(): CREATE OPERATION REFINEMENT]]]");
				}
			}

			// artefact.umlClassDiagram.CreateAssociation
			if (consPrimitive instanceof CreateAssociation) {
				//CreateAssociation crAsso = (CreateAssociation) consPrimitive;

				//System.out.println("[[ProductLineConstructionAJ.generate(): CREATE ASSOCIATION");
				//out = factory.createAssociation(crAsso, feat, classesName);
				//System.out.println("ProductLineConstructionAJ.generate(): CREATE ASSOCIATION]]]");
			}

			if (factory.getPrintFile() != null) {
				factory.getPrintFile().getOut().close();
				factory.setPrintFile(null);
			}
		}
	}

	public String getFeatureLvl0XML(	Feature feat,
			boolean isAbstract,
			boolean isMandatory	) {
		String xml = "";

		if (factory.featuresAspects.containsKey(feat)) {
			ArrayList<String> aspects = factory.featuresAspects.get(feat);

			xml += "<and ";
			xml += isAbstract ? "abstract=\"true\" " : "";
			xml += isMandatory ? "mandatory=\"true\" " : "";
			xml += "name=\""+feat.getId()+"\" >"+"\n";

			for(String aspect : aspects) {
				xml += "<feature mandatory=\"true\" name=\""+aspect+"\""+" />"+"\n";
			}
			xml += "</and>"+"\n";
		} else {
			xml += "<feature ";
			xml += isAbstract ? "abstract=\"true\" " : "";
			xml += isMandatory ? "mandatory=\"true\" " : "";
			xml += "name="+"\""+feat.getId()+"\""+" />"+"\n";
		}

		return xml;
	}

	public String getFeatureLvl2XML(	Feature feat,
			boolean isAbstract,
			boolean isMandatory	) {
		String xml = "";

		if (factory.featuresAspects.containsKey(feat)) {
			ArrayList<String> aspects = factory.featuresAspects.get(feat);

			xml += "<and ";
			xml += isAbstract ? "abstract=\"true\" " : "";
			xml += isMandatory ? "mandatory=\"true\" " : "";
			xml += "name=\""+feat.getName()+"\" >"+"\n";

			for(String aspect : aspects) {
				xml += "<feature mandatory=\"true\" name=\""+aspect+"\""+" />"+"\n";
			}
			xml += "</and>"+"\n";
		} else {
			xml += "<feature ";
			xml += isMandatory ? "mandatory=\"true\" " : "";
			xml += "name=\""+feat.getName()+"\" />"+"\n";
		}

		return xml;
	}

	/**
	 * AspectJ
	 * @param feat
	 * @return
	 */
	public static String getFeaturePackageName(Feature feat) {
		String pckg = "";

		for(Trigger trigger : feat) {
			ConstructionPrimitive consPrimitive = ((StatementPrimitive) trigger).getPrimitive();

			if (consPrimitive instanceof CreatePackage) {
				CreatePackage createPackage = (CreatePackage) consPrimitive;

				pckg = createPackage.getName();
				break;
			}
		}

		return pckg.replace('.', '_');
	}

	/**
	 * AspectJ
	 * @param pName
	 * @return name of the package, ending with '_' if it doesn't correspond to the empty string (default package)
	 */
	public static String completePackageName(String pName) {
		return pName.equals("") ? pName : pName+"_";
	}

	/**
	 * AspectJ
	 * @param fName
	 * @return name of the feature in accordance with AspectJ conventions
	 */
	public static String transformFeatureName(String fName) { // decomposition of String.replaceAll(regex, replacement)
		StringBuilder sb = new StringBuilder();
		char[] ca = fName.toCharArray();
		int l = ca.length, i = 0;

		while(i < l) {
			if (ca[i] == '_') {
				if (i < l-1) {
					sb.append(Character.toUpperCase(ca[i+1]));
					i++;
				}
			} else {
				sb.append(ca[i]);
			}
			i++;
		}

		return sb.toString();
	}

	// getters and setters

	/**
	 * @return
	 */
	public boolean isIndentXMLModel() {
		return indentXMLModel;
	}

	/**
	 * @param indentXMLModel
	 */
	public void setIndentXMLModel(boolean indentXMLModel) {
		ProductLineConstructionAJ.indentXMLModel = indentXMLModel;
	}

	/**
	 * @return
	 */
	public Collection<Product> getProducts() {
		return products;
	}

	/**
	 * @param products
	 */
	@Override
	public void setProducts(Collection<Product> products) {
		this.products = products;
	}

	/**
	 * @param readXmi
	 */
	public void setReadXmi(ReadXmiStrategy readXmi) {
		this.factory.setReadXmi(readXmi);
	}

	/**
	 * @author Simon Grandsire
	 * Indents XML code
	 */
	class IndentHandler extends DefaultHandler {
		protected String indent;
		protected PrintStream out;
		protected boolean varTag = false;

		public IndentHandler (PrintStream out) {
			indent = "";
			this.out = out;
		}

		public void startDocument () throws SAXException {
			out.println("<?xml version=\"1.0\" encoding=\"UTF-8\" standalone=\"no\"?>");
		}

		public void startElement (String uri, String localName, String qName,
				Attributes attributes) throws SAXException {

			out.print(indent + "<" + qName);

			for (int i=0, lg=attributes.getLength(); i<lg; ++i)
				out.print(" " + attributes.getQName(i) + "=\"" + attributes.getValue(i) + "\"");

			if (qName.equalsIgnoreCase("feature")) {
				out.println(" />");
			} else {
				if (qName.equalsIgnoreCase("var"))
					out.print(">");
				else
					out.println(">");
			}

			indent += "\t";
		}

		public void endElement (String uri, String localName, String qName)
				throws SAXException {

			indent = indent.substring(0, indent.length() - 1);

			if (!qName.equalsIgnoreCase("feature")) {
				if (qName.equalsIgnoreCase("var"))
					out.println(/*indent + */"</" + qName + ">");
				else
					out.println(indent + "</" + qName + ">");
			}
		}

		public void characters (char[] ch, int start, int length)
				throws SAXException {

			String s = new String(ch, start, length);

			if (!s.matches("^\\s*$"))
				out.print(/*indent + */s.trim());
		}

		public void fatalError (SAXParseException e) throws SAXException {
			System.err.println("\n***** Erreur fatale à la ligne " +
					e.getLineNumber() + ", \tau colonne " +
					e.getColumnNumber());
		}

		public void error (SAXParseException e) throws SAXException {
			System.err.println("\n***** Erreur non fatale à la ligne " +
					e.getLineNumber() + ", \tau colonne " +
					e.getColumnNumber());
		}

		public void indentXml (String xml, PrintStream out) {
			IndentHandler handler = new IndentHandler(out);

			try {
				XMLReader xmlReader = XMLReaderFactory.createXMLReader();

				xmlReader.setContentHandler(handler);
				xmlReader.setErrorHandler(handler);

				InputStream xmlStream = new ByteArrayInputStream(xml.getBytes("UTF-8"));
				xmlReader.parse(new InputSource(xmlStream));
			} catch (SAXException e) {
				System.err.println("Problème(s) SAX : " + e.getMessage());
				System.exit(0);
			} catch (IOException e) {
				System.err.println("Problème(s) d'entrée : " +
						e.getMessage());
				System.exit(0);
			}
		}
	}
}